<!--
 * @Author: ellery
 * @Date: 2022-09-01 11:04:29
 * @LastEditTime: 2022-09-01 11:07:09
 * @LastEditors: ellery
 * @Description: 简陋的日历 --- 目前月的插槽返回年月日，周的插槽返回周期/开始&结束日期,年的插槽返回年月;周期的计算是从每年的月初开始，由weekBegin控制周期从星期几开始计算，若第一周不足7天，将不会取上一年的，若是12月份的周期，最后一周不够7天将会取下年的日期。
 * @FilePath: \vue2-eslint-model\src\components\HumbleCalendar\HumbleCalendar.vue
 * 愿天堂没代码
-->
<template>
  <div class="layout">
    <div class="header">
      <a-select v-model="years" class="selectYears" @change="changeYears">
        <a-select-option
          v-for="item in yearsList"
          :key="item.id"
          :value="item.id"
        >
          {{ item.name }}
        </a-select-option>
      </a-select>
      <a-select v-model="months" class="selectMonths" @change="changeMonths">
        <a-select-option
          v-for="item in monthsList"
          :key="item.id"
          :value="item.id"
        >
          {{ item.name }}
        </a-select-option>
      </a-select>
      <div v-if="dataTypesList.length" class="btnTar">
        <a-radio-group v-model="dataTypes" @change="changeTar">
          <a-radio-button
            v-for="item in dataTypesList"
            :key="item.id"
            :value="item.id"
          >
            {{ item.name }}</a-radio-button>
        </a-radio-group>
      </div>
    </div>
    <div class="content">
      <!-- 月插槽 -->
      <template v-if="dataTypes == 'month'">
        <div class="dayLayout">
          <div class="cycle">
            <div v-for="item in cycleList" :key="item" class="list">
              {{ item }}
            </div>
          </div>
          <div class="daysContainer">
            <div
              v-for="item in daysList"
              :key="item.id"
              :class="[item.time == selectDayIndex ? 'selectbg' : '', 'list']"
              :style="monthHeight"
              @click.stop="clickStopDaySelectIndex(item)"
            >
              <div
                :class="['topRight', item.isgray ? 'colorGy' : '']"
                :style="{ 'font-weight': topLeftWeight }"
              >
                {{ item.name }}
              </div>
              <div
                v-if="isVacation"
                :class="['topLeft', handleVacationColor(item)]"
                :style="{ 'font-weight': topRightWeight }"
              >
                {{ handleVacationList(item.time) }}
              </div>
              <div
                v-if="festival"
                :class="['topLeft', handleVacationColor(item)]"
                :style="{ 'font-weight': topRightWeight }"
              >
                {{ handleFestivalList(item.time) }}
              </div>
              <div class="container">
                <slot name="dateCellRender" :item="item" />
              </div>
            </div>
          </div>
        </div>
      </template>
      <!-- 周插槽 -->
      <template v-if="dataTypes == 'week'">
        <div class="weekLayout">
          <div
            v-for="(item, index) in hanlderWeekList"
            :key="index"
            :class="[
              index == selectIndex ? 'selectbg' : '',
              handleNowMonth(item[0]) == months && isWeekSlot
                ? 'weekIndexs'
                : '',
              'list'
            ]"
            :style="weekHeight"
            @click.stop="clickStopSelectIndex(handleNowMonth(item[0]), index)"
          >
            <div class="topRight">
              <div class="w">第{{ index }}周</div>
              <div class="tiems">
                {{ formatDate(item[0] + " 00:00:00") }}-{{
                  formatDate(item[item.length - 1] + " 00:00:00")
                }}
              </div>
            </div>
            <div class="container">
              <slot
                name="weekCellRender"
                :item="item[0] + '~' + item[item.length - 1]"
              />
            </div>
          </div>
        </div>
      </template>
      <!-- 年插槽 -->
      <template v-if="dataTypes == 'year'">
        <div class="yearLayout">
          <div
            v-for="(item, index) in monthsList"
            :key="item.id"
            :class="[index == selectIndex ? 'selectbg' : '', 'list']"
            :style="yearHeight"
            @click.stop="clickStopSelectIndex(item.id, index)"
          >
            <div class="topRight">{{ MONTHS[item.id - 1] }}</div>
            <div class="container">
              <slot name="monthCellRender" :item="years + '-' + item.ch" />
            </div>
          </div>
        </div>
      </template>
    </div>
  </div>
</template>

<script>
const MONTHS = [
  "一月",
  "二月",
  "三月",
  "四月",
  "五月",
  "六月",
  "七月",
  "八月",
  "九月",
  "十月",
  "十一月",
  "十二月"
];
export default {
  name: "HumbleCalendar",
  props: {
    // 默认显示时间
    defaultValue: {
      type: String,
      default: ""
    },
    // 插槽类型
    dataTypesList: {
      type: Array,
      default: () => [
        {
          id: "month",
          name: "月"
        },
        {
          id: "week",
          name: "周"
        },
        {
          id: "year",
          name: "年"
        }
      ]
    },
    // 月插槽格子的高度
    monthHeight: {
      type: String,
      default: "height:60px"
    },
    // 周插槽格子的高度
    weekHeight: {
      type: String,
      default: "height:80px"
    },
    // 年插槽格子的高度
    yearHeight: {
      type: String,
      default: "height:80px"
    },
    // 周插槽是否全部展示,true->全部
    isWeekSlot: {
      type: Boolean,
      default: false
    },
    // 周期从周几开始计算
    weekBegin: {
      type: String,
      default: "1" // 周日是0
    },
    // 是否只显示节日名
    festival: {
      type: Boolean,
      default: false
    },
    // 是否显示班、休等标记,false->不显示
    isVacation: {
      type: Boolean,
      default: false
    },
    // 是否获取节假日
    getVacation: {
      type: Boolean,
      default: false
    },
    // 是否显示班字,false->不显示
    isShowWork: {
      type: Boolean,
      default: false
    },
    // 左上角字体粗细
    topLeftWeight: {
      type: String,
      default: "550"
    },
    // 右上角字体粗细
    topRightWeight: {
      type: String,
      default: "100"
    },
    // 存放三个月内的是否假期
    vacationList: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      MONTHS,
      years: "",
      yearsList: [],
      months: "",
      monthsList: [],
      daysList: [],
      cycleList: ["一", "二", "三", "四", "五", "六", "日"],
      weekList: {},
      dataTypes: "month", // 默认显示每日
      selectIndex: -1,
      selectDayIndex: "" // 月专用下标
    };
  },
  computed: {
    /**
     * @description:计算每个月的周期,若要全部展示，上面的屏蔽就行了
     * @param {}
     * @return {Object}
     */

    hanlderWeekList() {
      let temObj = {};
      if (this.isWeekSlot) {
        temObj = this.weekList;
      } else {
        for (const k in this.weekList) {
          if (this.handleNowMonth(this.weekList[k][0]) == this.months) {
            temObj[k] = this.weekList[k];
          }
        }
      }

      return temObj;
    }
  },
  mounted() {
    this.handleNowTimes();
  },
  methods: {
    //* *********************初始化begin************************************* */
    // 获取当前年月
    handleNowTimes() {
      let date = new Date();
      if (this.defaultValue.length) {
        date = new Date(this.defaultValue);
      }
      this.years = date.getFullYear();
      this.months = date.getMonth() + 1;
      for (let i = 1; i <= 12; i++) {
        const tem = {
          id: i,
          ch: `${i > 9 ? i : `0${i}`}`,
          name: `${i}月`
        };
        this.monthsList.push(tem);
      }
      this.assembleYearsList();
      this.showDays();
      this.selectDayIndex = this.formatDate(new Date(), "{y}-{m}-{d}");
    },
    //* *********************初始化end************************************* */
    //* *********************事件begin************************************* */
    // 年改变
    changeYears() {
      this.assembleYearsList();
      if (this.dataTypes == "month") {
        this.showDays();
      }
      if (this.dataTypes == "week") {
        this.assembleWeekList();
      }
    },
    // 月改变
    changeMonths() {
      this.showDays();
    },
    // 插槽改变
    changeTar() {
      this.initData();
      switch (this.dataTypes) {
        case "month":
          this.showDays();
          break;
        case "week":
          this.assembleWeekList();
          break;
        case "year":
          this.selectIndex = this.monthsList.findIndex(
            (ele) => ele.id == this.months
          );
          break;
        default:
          break;
      }
    },
    // 周/年点击事件
    clickStopSelectIndex(item, index) {
      this.months = item;
      this.selectIndex = index;
    },
    // 月点击事件
    clickStopDaySelectIndex(item) {
      // console.log(item, "=>月点击事件");
      const months = new Date(item.time).getMonth() + 1;
      const years = new Date(item.time).getFullYear();
      if (months != this.months || years != this.years) {
        this.months = months;
        this.years = years;
        this.changeMonths();
      }
      this.selectDayIndex = item.time;
      // console.log("selectDayIndex",this.selectDayIndex);
    },
    //* *********************事件end************************************* */
    //* *********************调用begin************************************* */
    initData() {
      this.selectIndex = -1;
      this.selectDayIndex = "";
    },
    // 获取当前，上一个月，下一个月的日期
    showDays() {
      this.$emit(
        "handleCalendar",
        `${this.years}-${this.months > 9 ? this.months : `0${this.months}`}`
      );
      this.daysList = [];
      const years = this.years;
      const months = this.months;
      let pervYears = years;
      let pervMonths = months - 1;
      let nextYears = years;
      let nextMonths = months + 1;
      if (months == 1) {
        pervYears = years - 1;
        pervMonths = 12;
      }
      if (months == 12) {
        nextYears = years + 1;
        nextMonths = 1;
      }
      const nowMonthdays = new Date(years, months, 0).getDate();
      const pervMothDays = new Date(pervYears, pervMonths, 0).getDate();
      // console.log(nowMonthdays, "=>days", pervMothDays);
      // 获取当前月的第一天为周几
      // 注:这里日历是从周一开始,若要修改周六或者其他星期，请另外写方法兼容，记得将cycleList里的也修改了
      let firstWeek = new Date(`${years}-${months}-01`).getDay();
      if (firstWeek == 0) {
        firstWeek = 7;
      }
      // 当前月的日期
      const daysList = this.nowMonthDays(
        years,
        months,
        firstWeek,
        nowMonthdays
      );
      // 上一个月的日期
      const temPev = this.pervMonthDays(
        firstWeek,
        pervMothDays,
        pervYears,
        pervMonths
      );
      // 下个月的日期
      const temNext = this.nextMonthDays(
        daysList[daysList.length - 1].id,
        nextYears,
        nextMonths
      );
      // 合并
      this.daysList = [...temPev, ...daysList, ...temNext];
      // console.log(this.daysList, "=>合并");
    },
    /**
     * @description: 当前月的日期
     * @param {Number} years 年份
     * @param {Number} months 月份
     * @param {Number} firstWeek 月初时是星期几
     * @param {Number} nowMonthdays 当前月的天数
     * @return {Array}
     */

    nowMonthDays(years, months, firstWeek, nowMonthdays) {
      const daysList = [];
      let num = 0;
      for (let i = firstWeek; i <= 42; i++) {
        num += 1;
        if (num <= nowMonthdays) {
          const tem = {
            id: i - 1,
            name: `${num > 9 ? num : `0${num}`}`,
            time: `${years}-${months > 9 ? months : `0${months}`}-${
              num > 9 ? num : `0${num}`
            }`,
            isgray: false
          };
          daysList.push(tem);
        }
      }
      // console.log(daysList, "=>daysList");
      return daysList;
    },
    //
    /**
     * @description: 当前月份不是设定的星期时得获取上个月尾得天数补齐一周
     * @param {Number} firstWeek 从星期几开始计算
     * @param {Number} pervMothDays 上个月份
     * @param {Number} pervYears 若是从上年12月份获取天数的，pervYears是上年的年份否则是当前年份
     * @param {Number} pervMonths 若是从上年12月份获取天数的，pervMonths是上年的12月份否则是当前月份
     * @return {Array}
     */

    pervMonthDays(firstWeek, pervMothDays, pervYears, pervMonths) {
      const temPev = [];
      for (let j = firstWeek - 1; j > 0; j--) {
        const tem = {
          id: j - 1,
          name: `${pervMothDays - firstWeek + j + 1}`,
          time: `${pervYears}-${
            pervMonths > 9 ? pervMonths : `0${pervMonths}`
          }-${pervMothDays - firstWeek + j + 1}`,
          isgray: true
        };
        temPev.unshift(tem);
      }
      // console.log(temPev, "=>temPev");
      return temPev;
    },
    /**
     * @description: 当前月份天数+pervMonthDays()之后的天数不足42时，获取下个月处的天数
     * @param {Number} cnum 42-减去前月份天数+pervMonthDays()的天数
     * @param {Number} nextYears 若是从明年1月份获取天数的，nextYears是明年的年份否则是当前年份
     * @param {Number} nextMonths 若是从明年1月份获取天数的，nextMonths是明年的1月份否则是当前月份
     * @return {Array}
     */

    nextMonthDays(cnum, nextYears, nextMonths) {
      const temNext = [];
      for (let w = 1; w < 42 - cnum; w++) {
        const tem = {
          id: cnum + w,
          name: `${w > 9 ? w : `0${w}`}`,
          time: `${nextYears}-${
            nextMonths > 9 ? nextMonths : `0${nextMonths}`
          }-${w > 9 ? w : `0${w}`}`,
          isgray: true
        };
        temNext.push(tem);
      }
      // console.log(temNext, "=>temNext");
      return temNext;
    },
    // 根据当前年份获取所有周
    assembleWeekList() {
      // 第一周不满7天的去除，最后一周不够7天取下年的日期
      const temWeek = this.getWeek(this.years);
      const newWeek = {};
      if (temWeek[1].length < 7) {
        delete temWeek[1];
        Object.keys(temWeek).map((k) => {
          newWeek[k - 1] = temWeek[k];
        });
      } else {
        Object.keys(temWeek).map((k) => {
          newWeek[k] = temWeek[k];
        });
      }
      const lastList = newWeek[Object.keys(newWeek).length];
      const len = lastList.length;
      if (len < 7) {
        const calc = 7 - len;
        for (let i = 1; i <= calc; i++) {
          const tem = `${this.years + 1}-01-${i > 9 ? i : `0${i}`}`;
          lastList.push(tem);
        }
      }
      this.weekList = newWeek;
      // console.log(this.weekList, "=>?");
    },
    /**
     * @description: 获取当前年的周期
     * @param {String | Number} year
     * @return {*} Object
     * @record 判断是否存在该周期键值对，若不存在赋予数组；存在时，判断是否从周?(weekBegin)开始计算，不是的存放起来，是的，新增键值对存放
     */
    getWeek(year) {
      const days = this.getEveryDay(year || new Date().getFullYear());
      const weeks = {};
      for (let i = 0; i < days.length; i++) {
        const weeksKeyLen = Object.keys(weeks).length;
        const daySplit = days[i].split("_");
        if (weeks[weeksKeyLen] == undefined) {
          weeks[weeksKeyLen + 1] = [daySplit[0]];
        } else {
          if (daySplit[1] == this.weekBegin) {
            weeks[weeksKeyLen + 1] = [daySplit[0]];
          } else {
            weeks[weeksKeyLen].push(daySplit[0]);
          }
        }
      }
      return weeks;
    },
    /**
     * @description: 计算出当前年的每天日期
     * @param {String | Number} year
     * @return {*} Array
     */

    getEveryDay(year) {
      const formatNumber = (n) => {
        return n.toString().length > 1 ? n : "0" + n;
      };
      const dates = [];
      for (var i = 1; i <= 12; i++) {
        for (var j = 1; j <= new Date(year, i, 0).getDate(); j++) {
          dates.push(
            year +
              "-" +
              formatNumber(i) +
              "-" +
              formatNumber(j) +
              "_" +
              new Date([year, i, j].join("-")).getDay()
          );
        }
      }
      return dates;
    },
    /**
     * @description: 获取月份
     * @param {String | Number} times 时间
     * @return {*} String
     */

    handleNowMonth(times) {
      return new Date(times).getMonth() + 1;
    },
    // 根据当前年份，将其二十年内的装进去
    assembleYearsList() {
      this.yearsList = [];
      this.initData();
      const calc = Number(this.years) - 10;
      for (let i = 0; i < 20; i++) {
        const years = calc + i;
        const tem = {
          id: years,
          name: `${years}年`
        };
        this.yearsList.push(tem);
      }
      this.$forceUpdate();
    },
    /**
     * @description: 时间格式化
     * @param {String} time 需要带上时分秒,如："2022-08-01 10:01:01"
     * @param {String} cFormat 格式化
     * @return {String}
     */

    formatDate(time, cFormat = "{m}/{d}") {
      if (arguments.length === 0) {
        return null;
      }
      const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}";
      let date;
      if (typeof time === "object") {
        date = time;
      } else {
        if (("" + time).length === 10) time = parseInt(time) * 1000;
        date = new Date(time);
      }
      const formatObj = {
        y: date.getFullYear(),
        m: date.getMonth() + 1,
        d: date.getDate(),
        h: date.getHours(),
        i: date.getMinutes(),
        s: date.getSeconds(),
        a: date.getDay()
      };
      const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
        let value = formatObj[key];
        if (key === "a") {
          return ["日", "一", "二", "三", "四", "五", "六"][value];
        }
        if (result.length > 0 && value < 10) {
          value = "0" + value;
        }
        return value || 0;
      });
      return time_str;
    },
    // 处理假期标记
    handleVacationList(key) {
      if (this.vacationList[key]) {
        // console.log("进入", this.vacationList);
        return this.vacationList[key].name;
      } else {
        return this.isShowWork ? "班" : "";
      }
    },
    // 处理标记字体颜色
    handleVacationColor(item) {
      if (!this.getVacation) return "colorBlack";
      if (item.isgray) {
        return "colorGy";
      } else {
        console.log(
          this.vacationList[item.time].num,
          "=>this.vacationList[item.time]"
        );
        return this.vacationList[item.time] &&
          this.vacationList[item.time].num == "0"
          ? "colorBlack"
          : "colorRed";
      }
    },
    // 处理显示节日名 0正常上班，1节假日，2补班，3正常双休
    handleFestivalList(key) {
      if (!this.vacationList[key]) return "";
      const num = this.vacationList[key].num;
      const explain = this.vacationList[key].explain;
      if (num == "1") {
        return explain.indexOf("节") > -1 ? explain.split("节")[0] : explain;
      } else {
        return "";
      }
    }
    //* *********************调用end************************************* */
  }
};
</script>

<style scoped lang="scss">
.layout {
  padding: 10px 0;
}
.header {
  text-align: right;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
.selectYears {
  width: 100px;
}
.selectMonths {
  width: 80px;
  margin: 0 10px;
}
.content {
  // border: 1px solid red;
  padding: 10px;
  padding-bottom: 0;
  .yearLayout {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    .list {
      width: 32%;
      border-top: 2px solid #e8e8e8;
      // height: 120px;
      position: relative;

      &:hover {
        background: #f1f1f1;
      }
      .topRight {
        position: absolute;
        z-index: 100;
        right: 5px;
        top: 5px;
      }
    }
  }
  .weekLayout {
    display: flex;
    flex-wrap: wrap;
    // justify-content: space-between;
    .list {
      border-top: 2px solid #e8e8e8;
      width: 15%;
      // height: 120px;
      position: relative;
      margin-right: 10px;
      &:hover {
        background: #f1f1f1;
      }
      .topRight {
        position: absolute;
        z-index: 100;
        right: 5px;
        top: 5px;
        font-size: 12px;
        .w {
          text-align: right;
        }
        // .tiems{
        // }
      }
      .container {
        display: flex;
        justify-content: flex-end;
      }
    }
    .weekIndexs {
      background: #fff0181e;
    }
  }
  .dayLayout {
    .cycle {
      font-size: 16px;
      font-weight: 550;
      display: flex;
      margin-bottom: 10px;
      .list {
        width: 14%;
        text-align: center;
      }
    }
    .daysContainer {
      display: flex;
      flex-wrap: wrap;
      justify-content: space-between;
      .list {
        border-top: 2px solid #e8e8e8;
        width: 13%;
        // height: 60px;
        position: relative;
        &:hover {
          background: #f1f1f1;
        }
        .topRight {
          position: absolute;
          z-index: 100;
          right: 5px;
          top: 5px;
          // font-weight: 550;
        }
        .topLeft {
          position: absolute;
          z-index: 100;
          left: 5px;
          top: 5px;
          font-size: 12px;
          // font-weight: 100;
        }
      }
    }
  }

  .container {
    overflow: auto;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    margin-top: 5px;
  }
  .selectbg {
    background: #e6f7ff;
    border-top-color: #1890ff !important;
    .topRight {
      color: #1890ff;
    }
  }
  .colorGy {
    color: #e8e8e8;
  }
  .colorRed {
    color: #f73131;
  }
  .colorBlack {
    color: #626675;
  }
}
</style>
